package org.nvframe.event;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.nvframe.component.Component;
import org.nvframe.entity.Entity;
import org.nvframe.event.eventtype.*;
import org.nvframe.exception.NVFrameException;

/**
 * 
 * @author Nik Van Looy
 */
public class EventService {
	
	private static EventService _instance = new EventService();
	
	public static enum EVENT_TYPE {
		IDLE, 
		BUSY, 
		DEATH, 
		UNKNOWN, 
		COLLISION, 
		DAMAGE,
		STATS,
		GAMEUPDATE,
		GAMERENDER, 
		SOUND,
		MOTION
	};

	public static EventService getInstance() {
		return _instance;
	}
	
	private Map<Entity, ArrayList<EventListener>> entityListeners;
	private List<EventListener> listenersWithOwner;
	private List<EventListener> listenersWithoutOwner;
	
	private EventService() {
		entityListeners = new HashMap<Entity, ArrayList<EventListener>>();
		listenersWithOwner = new ArrayList<EventListener>();
		listenersWithoutOwner = new ArrayList<EventListener> ();
	}
	
	/**
	 * Add an eventListener
	 * any class that implements EventListener can be added
	 * 
	 * @param listener
	 */
	public void addEventListener(EventListener listener) {
		Entity owner;
		
		if(listener instanceof Entity)
			owner = (Entity) listener;
		else if(listener instanceof Component)
			owner = ((Component) listener).getOwner();
		else // this is for classes that have nothing to do whith entities nor components
			owner = null;

		if(owner != null) {		
			listenersWithOwner.add(listener);
			
			if(!entityListeners.containsKey(owner))
				entityListeners.put(owner, new ArrayList<EventListener>());
			
			entityListeners.get(owner).add(listener);
		}
		else
			listenersWithoutOwner.add(listener);
	}
	
	/**
	 * Remove a specified eventListener
	 * 
	 * @param listener
	 */
	public void removeEventListener(EventListener listener) {
		Entity owner;
		
		if(listener instanceof Entity)
			owner = (Entity) listener;
		else if(listener instanceof Component)
			owner = ((Component) listener).getOwner();
		else // this is for classes that have nothing to do whith entities nor components
			owner = null;
		
		if(entityListeners.containsKey(owner) && entityListeners.get(owner).contains(listener))
			entityListeners.get(owner).remove(listener);
		
		if(listenersWithOwner.contains(listener))
			listenersWithOwner.remove(listener);
	}
	
	/**
	 * Fires a specified event to all listeners
	 * 
	 * @param event The event to pass to the listener(s)
	 */
	public void fireEvent(Event event) {
		// make a copy to avoid ConcurrentModificationException
		ArrayList<EventListener> listenersColCpy = new ArrayList<EventListener> ();

		listenersColCpy.addAll(listenersWithOwner);
		listenersColCpy.addAll(listenersWithoutOwner);
		
		fireEvent(event, listenersColCpy);
	}

	/**
	 * Fires a specified event
	 * 
	 * @param event The event to pass to the listener(s)
	 * @param mustHaveOwner Specifies that the Listener Object is a Subclass from Component or Entity
	 */
	public void fireEvent(Event event, boolean mustHaveOwner) {
		// make a copy to avoid ConcurrentModificationException
		ArrayList<EventListener> listenersColCpy = new ArrayList<EventListener> ();
		
		if(mustHaveOwner)
			listenersColCpy.addAll(listenersWithOwner);
		else
			listenersColCpy.addAll(listenersWithoutOwner);
		
		fireEvent(event, listenersColCpy);
	}
	
	/**
	 * Fires an event to a specified Entity object
	 * 
	 * @param event The event to pass to the listener(s)
	 * @param target The target Entity to fire the event to
	 */
	public void fireEvent(Event event, Entity target) {
		if(!target.isEnabled())
			return;
		
		// make a copy to avoid ConcurrentModificationException
		ArrayList<EventListener> listenersColCpy = new ArrayList<EventListener>();
		
		listenersColCpy.addAll(entityListeners.get(target));
		
		fireEvent(event, listenersColCpy);
	}
	
	/**
	 * Fires an event to a specified collection of Listeners
	 * 
	 * @param event The Event that will be fired
	 * @param listeners The collections of EventListeners
	 */
	private void fireEvent(Event event, ArrayList<EventListener> listeners) {
		
		for(EventListener listener : listeners) {
			if(!event.hasListener(listener))
				continue;
			
			// listener is entity or component from entity? => check if the entity isnt disabled
			Entity owner = null;
			
			if(listener instanceof Entity)
				owner = (Entity) listener;
			else if(listener instanceof Component)
				owner = ((Component) listener).getOwner();
			
			if(owner != null)
				if(!owner.isEnabled())
					continue;
			
			try {
				dispatchEvent(event, listener);
			}
			catch(NVFrameException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	/**
	 * Fires the event
	 * this method does Type Casting so the implementation code
	 * can work with the correct concrete classes
	 * 
	 * @param event The event to pass to the listener(s)
	 * @param listener The listener to pass the event to
	 * @throws NVFrameException
	 */
	private void dispatchEvent(Event event, EventListener listener) throws NVFrameException {
		
		try {
			
			switch(event.getEventType()) {
				case COLLISION:
					((CollisionListener) listener).onCollision((CollisionEvent) event);
					break;
				case DAMAGE:
					((DamageListener) listener).damageReceived((DamageEvent) event);
					break;
				case DEATH:
					((DeathListener) listener).onDeath((DeathEvent) event);
					break;
				case STATS:
					((StatsListener) listener).statsChanged((StatsEvent) event);
					break;
				case GAMEUPDATE:
					((UpdateListener) listener).onUpdate((UpdateEvent) event);
					break;
				case GAMERENDER:
					((RenderListener) listener).onRender((RenderEvent) event);
					break;
				case SOUND:
					((SoundListener) listener).playSound((SoundEvent) event);
					break;
				case MOTION:
					((MotionListener) listener).onMotion((MotionEvent) event);
					break;
				default:
					System.out.println("incorrect listener");
					break;
			}
		} catch (NVFrameException e) {
			throw new NVFrameException("could not dispatch event, type: " + event.getEventType().toString());
		}
	}
}
